/**
 * @file j1939_stack.c
 * @author Jomb (jomb.liu@solinftec.com)
 * @brief
 * @version 0.1
 * @date 2022-06-08
 *
 * @copyright Copyright (c) 2022
 *
 */

#include "j1939_stack.h"
#include "j1939_object.h"
#include "j1939_api.h"
#include "string.h"

static can_send_t can_send; /* send j1939 message */
static j1939_mutex_take_t mutex_take;
static j1939_mutex_release_t mutex_release;

static struct struct_j1939_data j1939_data = {0};

/**
 * @brief j1939_data write method
 *
 * @param pgn
 * @param spn
 * @param data
 * @param size
 * @return int
 */
int j1939_write_local(unsigned int pgn,
                      unsigned int spn,
                      void *data,
                      unsigned int size)
{
    int err = J1939_SUCCESS;
    const j1939_param_group *p_pgn;
    j1939_spn_callback_t *cb_group;
    j1939_spn_callback_t cb;
    j1939_doubtful_object *p_spn;

    p_pgn = j1939_scan_png_group(pgn, &err, &cb_group);

    if (err != J1939_SUCCESS)
    {
        return err;
    }

    for (int i = 0; i < p_pgn->b_spn_count; i++)
    {
        if (spn == p_pgn->spn[i].spn_num)
        {
            p_spn = &p_pgn->spn[i];
            cb = cb_group[i];
            break;
        }
    }

    if (p_spn == NULL)
    {
        LOG_E(0x2001, "j1939_write err, no spn object :", spn);
        return -1;
    }

    // if (p_spn->bit_len % 8 != 0)
    // {
    //     LOG_E(0x3001, "j1939_write err, not support bit size :", p_spn->bit_len);
    //     return -1;
    // }

    // if ((p_spn->bit_len / 8) < size) /// xxx todo support object bit size < 8
    // {
    //     LOG_E(0x3000, "j1939_write err, data len out of object,object size :", (p_spn->bit_len / 8));
    //     return -1;
    // }

    // xxx todo support max and min judgement
    if (mutex_take != NULL)
    {
        mutex_take(J1939_WRITE_MUTEX);
    }

    memcpy(p_spn->object, data, size);

    if (mutex_release != NULL)
    {
        mutex_release(J1939_WRITE_MUTEX);
    }

    if (cb != NULL)
    {
        err = cb(pgn, spn, 0);
        if (err != J1939_SUCCESS)
        {
            return err;
        }
    }
    return err;
}

/**
 * @brief
 *
 * @param pgn
 * @param spn
 * @param data
 * @param size
 * @return int
 */
int j1939_read_local(unsigned int pgn,
                     unsigned int spn,
                     void *data,
                     unsigned int size)
{
    int err = J1939_SUCCESS;
    const j1939_param_group *p_pgn;
    j1939_spn_callback_t *cb_group;
    j1939_doubtful_object *p_spn;

    p_pgn = j1939_scan_png_group(pgn, &err, &cb_group);

    if (err != J1939_SUCCESS)
    {
        return err;
    }

    for (int i = 0; i < p_pgn->b_spn_count; i++)
    {
        if (spn == p_pgn->spn[i].spn_num)
        {
            p_spn = &p_pgn->spn[i];
            break;
        }
    }

    if (p_spn == NULL)
    {
        LOG_E(0x2001, "j1939_write err, no spn object :", spn);
        return -1;
    }

    // if ((p_spn->bit_len / 8) > size) /// xxx todo support object bit size < 8
    // {
    //     LOG_E(0x3000, "j1939_write err, object size out of expected size ,object size  :", (p_spn->bit_len / 8));
    //     return -1;
    // }
    if (mutex_take != NULL)
    {
        mutex_take(J1939_READ_MUTEX);
    }

    memcpy(data, p_spn->object, size);

    if (mutex_release != NULL)
    {
        mutex_release(J1939_READ_MUTEX);
    }

    return err;
}

int j1939_write_net(unsigned int pgn,
                    unsigned char property)
{
    int err = J1939_SUCCESS;
    const j1939_param_group *p_pgn;
    j1939_spn_callback_t *cb_group;
    j1939_message m = {0};
    unsigned char *buf = m.data;

    memset(&m,0,sizeof(j1939_message));
    
    p_pgn = j1939_scan_png_group(pgn, &err, &cb_group);

    if (err != J1939_SUCCESS)
    {
        return err;
    }

    for (int i = 0; i < p_pgn->b_spn_count; i++)
    {
        m.un_id.id = (pgn << 8) | (property << 26) | p_pgn->spn[i].recver;
        m.len += p_pgn->spn[i].bit_len / 8;
        unsigned int tmp = 0;

        if (mutex_take != NULL)
        {
            mutex_take(J1939_READ_MUTEX);
        }

        if (p_pgn->spn[i].data_type == J1939_FLOAT_T)
        {
            tmp = (unsigned int)((*(float *)(p_pgn->spn[i].object) - *(float *)(p_pgn->spn[i].offset)) / *(float *)(p_pgn->spn[i].factor));
        }
        else if (p_pgn->spn[i].data_type == J1939_UINT16_T)
        {
            tmp = (unsigned int)((*(unsigned short *)(p_pgn->spn[i].object) - *(unsigned short *)(p_pgn->spn[i].offset)) / *(unsigned short *)(p_pgn->spn[i].factor));
        }
        /// xxx todo  support other type

        if (mutex_release != NULL)
        {
            mutex_release(J1939_READ_MUTEX);
        }

        memcpy(buf,&tmp, (p_pgn->spn[i].bit_len / 8));
        buf += (p_pgn->spn[i].bit_len / 8); //
    }

    if (can_send != NULL)
    {
        can_send(m);
    }
    return err;
}

/**
 * @brief
 *
 * @param m
 * @return int
 */
static int j1939_dest_add_mode_handler(j1939_message *m)
{
    int err = 0;
    unsigned int pgn = (unsigned int)((m->un_id.id >> 8) & 0x3FFFF);

    const j1939_param_group *p_pgn;
    j1939_spn_callback_t *cb_group;
    j1939_spn_callback_t cb;

    unsigned char *buf = m->data;

    p_pgn = j1939_scan_png_group(pgn, &err, &cb_group);

    if (err != J1939_SUCCESS)
    {
        return err;
    }

    for (int i = 0; i < p_pgn->b_spn_count; i++)
    {
        if (p_pgn->spn[i].bit_len <= 32)
        {
            unsigned int tmp = 0;

            memcpy(&tmp, buf, (p_pgn->spn[i].bit_len / 8));

            buf += (p_pgn->spn[i].bit_len / 8); //

            if (mutex_take != NULL)
            {
                mutex_take(J1939_READ_MUTEX);
            }

            if (p_pgn->spn[i].data_type == J1939_FLOAT_T)
            {

                *(float *)(p_pgn->spn[i].object) = tmp * *(float *)(p_pgn->spn[i].factor) + *(float *)(p_pgn->spn[i].offset);
            }
            else if (p_pgn->spn[i].data_type == J1939_UINT16_T)
            {
                *(unsigned short *)(p_pgn->spn[i].object) = tmp * *(unsigned short *)(p_pgn->spn[i].factor) + *(unsigned short *)(p_pgn->spn[i].offset);
            }
            /// xxx todo  support other type

            if (mutex_release != NULL)
            {
                mutex_release(J1939_READ_MUTEX);
            }  
            
            cb = cb_group[i];

            if (cb != NULL)
            {
                err = cb(pgn, p_pgn->spn[i].spn_num, 0);
                if (err != J1939_SUCCESS)
                {
                    return err;
                }
            }
        }
    }

    return err;
}

/**
 * @brief
 *
 * @param addr
 * @param j1939_send
 * @return int
 */
int j1939_init(char addr, can_send_t send, j1939_mutex_take_t take, j1939_mutex_release_t release)
{
    int res = 0;

    j1939_data.addr = addr;

    if (send != NULL)
    {
        can_send = send;
    }
    else
    {
        LOG_E(0x1000, "j1939_init error: send function is NULL", send);
        res = -1;
    }

    if (take != NULL)
    {
        mutex_take = take;
    }

    if (release != NULL)
    {
        mutex_release = release;
    }

    return res;
}

/**
 * @brief can recv dispatcher
 *
 * @param m
 * @return int
 */
int j1939_dispatch(j1939_message *m)
{
    int res = 0;

    // desk addr mode
    if (m->un_id.un_id.pf < 0xF0 && m->un_id.un_id.ps == j1939_data.addr)
    {
        j1939_dest_add_mode_handler(m);
    }
    else /// xxxx todo support broadcast mode
    {
    }
    return res;
}

int j1939_register_spn_cb(unsigned int pgn,
                          unsigned int spn,
                          j1939_spn_callback_t cb)
{
    int err = J1939_SUCCESS;
    const j1939_param_group *p_pgn;
    j1939_spn_callback_t *cb_group;

    p_pgn = j1939_scan_png_group(pgn, &err, &cb_group);

    if (err != J1939_SUCCESS)
    {
        return err;
    }

    for (int i = 0; i < p_pgn->b_spn_count; i++)
    {
        if (spn == p_pgn->spn[i].spn_num)
        {
            cb_group[i] = cb;
            break;
        }
    }

    return err;
}
